from django.shortcuts import render_to_response
from django.template import RequestContext
import urllib, httplib
import traceback

import demjson

def calais(request):
    """
    This is the primary view, which controls loading the submission
    page, reading in submitted content and rendering the response
    appropriately. The request will either be a post or a get, and
    the post means data should have been submitted, although we verify
    that the data meets our requirements before sending it any further.
    POST requests may be coming in via AJAX, in the case of a javascript
    enabled client, or by standard full page posts. This definition
    handles both, although the app is better looking with javascirpt
    enabled.
    """
    if request.method == "GET":
        context = RequestContext(request)
        response = render_to_response('calais/calais.html', context)  
        return response
    elif request.method == "POST":
        content = request.POST['content']
        if(content == ""):#empty strings are not submitted
            warning_message = "Please enter some text."
            context = RequestContext(request, {'message':warning_message})
#retry.html is a form identical to calais.html, but without surrounding html
            response = render_to_response('calais/retry.html', context)
        elif(len(content) <= 100):#Open Calais requirement
            warning_message = "Please enter more than 100 characters."
            context = RequestContext(request, {'content': content, 'message':warning_message})
            response = render_to_response('calais/retry.html', context)
        elif(len(content) >= 99800):#Open Calais requires <10,000
            content = content[:99796] + "..."
            response = get_response(request, content)
        else:
            response = get_response(request, content)
        return response

def rest_POST(content, api_key, xmlparams):
    """
    This method performs the actual HTTP connection to the OpenCalais,
    using the hardcoded API key, content submitted by the user and
    hardcoded process params. The content-type header is specified as
    required by OpenCalais, a connection is made to their server on
    port 80, and an HTTP post containing all of the parameters is 
    submitted. The response is retrieved, read in to an object and
    the connection is then closed.  The object is returned.
    
    The django-calais project found here was used as a reference when
    composing this method:
    http://media.jesselegg.com/djangocalais/
    """
    params = urllib.urlencode({'licenseID':api_key, 'content':content.encode('utf-8'), 'paramsXML':xmlparams})
    headers = {"Content-type":"application/x-www-form-urlencoded"}
    conn = httplib.HTTPConnection("api.opencalais.com:80")
    conn.request("POST", "/enlighten/rest/", params, headers)
    response = conn.getresponse()
    data = response.read()
    conn.close()
    return (data)

def shuffle_response_data(data):
    """
    This method takes the Python dictionary built by demjson from our 
    API data and makes it slightly easier to read for the template, 
    response.html, by building a dictionary with different groups of
    data separated into additional lists or dictionaries, which themselves 
    contain lists of values.
    """
    data_dict = {} #top level dictionary
    entity_types = [] #just a list of the categories present in our data
    entities = {} #dictionary where values or lists
    social_tags = [] #list of lists w/ length 2
    topics = [] #list of lists w/ length 2
#iterate over dictionary items, where k,v stand for key and value
    for k,v in data.items():
        if("_typeGroup" in v):
            if (v["_typeGroup"] == "entities"):
                if(v["_type"] not in entity_types):
                    entity_types.append(v["_type"])
#create empty list so we can append further lists as distinct values within
                    entities[v["_type"]] = []
#not all entities have a value for resolutions, but we want the link from those that do
                if("resolutions" in v):
#for some reason, the value associated with the key 'resolutions' appears to always be a list of length 1, containing a dictionary
                    entity_url = v["resolutions"][0]["id"]
                    entities[v["_type"]].append([v["relevance"], v["name"], entity_url])
                else:
                    entities[v["_type"]].append([v["relevance"], v["name"]])
            elif (v["_typeGroup"] == "socialTag"):
#items with category/name 'Other' will have no score/importance
                if(v["name"] == "Other"):
                    social_tags.append(["N/A", v["name"]])
                else:
                    social_tags.append([v["importance"], v["name"]])
            elif (v["_typeGroup"] == "topics"):
                if(v["categoryName"] == "Other"):
                    topics.append(["N/A", v["categoryName"]])
                else:
                    topics.append([v["score"], v["categoryName"]])
    
#sorting so the data looks clean when we iterate in the html template
    social_tags.sort()
    topics.sort(reverse=True)
    for etype in entity_types:
        entities[etype].sort(reverse=True)
    entity_types.sort(key=lambda x: x.lower())

#add everything in to the top level dictionary
    data_dict["entity_types"] = entity_types
    data_dict["entities"] = entities
    data_dict["social_tags"] = social_tags
    data_dict["topics"] = topics
    data_dict["date"] = data['doc']['meta']['submitionDate']
    return data_dict

def get_response(request, content):
    """
    This method takes the user submitted content, and with the above two
    methods, obtains and shuffles the OpenCalais analysis of the
    content.  This method also provides the majority of error handling,
    both by catching possible IO or data exceptions that can occur during
    the rest_POST method, writing the response appropriately.
    """
    api_key = "up9ntfhn2hwf9b4bz2x687dy"
    xmlparams ="""
<c:params xmlns:c="http://s.opencalais.com/1/pred/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <c:processingDirectives c:contentType="text/html" c:enableMetadataType="SocialTags" c:outputFormat="application/json" c:docRDFaccesible="true" > </c:processingDirectives> <c:userDirectives c:allowDistribution="true" c:allowSearch="true" c:externalID="court" c:submitter="robotoast"> </c:userDirectives> <c:externalMetadata> </c:externalMetadata> </c:params>
"""
    data=None  #initialize in case of failure, 'data' will evaluate to false
    try:
        data = demjson.decode(rest_POST(content, api_key, xmlparams))
    except Exception, e:
        excep_str = traceback.format_exc(1)#traceback string of the exception
        if(excep_str.find("<Exception>") > -1):#get Exception returned from OpenCalais
            warning_message = excep_str[excep_str.find("<Exception>") + 11:excep_str.find("</Exception>")]
        else:#Exceptions on our own server, might be communications or json decoding errors
            warning_message = e
        
        context = RequestContext(request, {'content': content, 'message': warning_message})
        return render_to_response('calais/retry.html', context)

    if(data):#if we make it here, we should assume the query was successful
        language = data['doc']['meta']['language']
        data_dict = shuffle_response_data(data)
        data_dict['content'] = content
        context = RequestContext(request, data_dict)
        response = render_to_response('calais/response.html', context)
#could occur where data is null at this point, although I haven't seen it
    else:
        warning_message = "I'm not sure why that failed... maybe the connection to the API died without throwing an exception."
        context = RequestContext(request, {'content': content, 'message': warning_message})
        return render_to_response('calais/retry.html', context)
    
    return response